/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.idei.ooparsing;

import edu.idei.ooparsing.rules.TextSegmentHandler;
import java.util.List;
import java.util.Stack;

import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * <p style="text-align:justify">
 * Subclase de <a href="http://commons.apache.org/digester/commons-digester-1.6/docs/api/org/apache/commons/digester/Digester.html">Digester</a>
 * que soporta reglas que implementan la interfaz
 * {@link TextSegmentHandler}, causando que el método callback
 * {@link TextSegmentHandler#textSegment(String)} se invoque para cada
 * regla que cumpla. El fin principal de esta subclase es poder procesar documentos
 * XML del tipo "document-markup".
 * <p style="text-align:justify">
 * Este tipo de documentos XML posee elementos XML cuyo cuerpo contiene, a
 * su vez, elementos embebidos. Por ejemplo, <code>&lt;p&gt;Hola, &lt;i&gt;este es&lt;/i&gt; un
 * tag embebido.&lt;/p&gt;</code>.
 * <p style="text-align:justify">
 * El principal uso de un Digester es procesar archivos de configuración XML. Tales
 * archivos, comúnmente no poseen elementos de texto con otros elementos
 * embebidos. El comportamiento estandar de un Digester es juntar todos esos
 * elementos embebidos en el cuerpo de un elemento (en lugar de un "segmento")
 * y presentarlos a la Rule como una única cadena.
 * <p style="text-align:justify">
 * Esta clase, extiende un Digester para resolver este problema.
 */
public class TextSegmentDigester extends Digester {

	private ODTParser parserx;
	private String document;
	private long documentSize;
	private long parsed = 38;
	private int currentPercent;
	private Stack<ElementHasChildren> st = new Stack<ElementHasChildren>();

	/**
	 * <p style="text-align:justify">
	 * Construye un "Digester" dado el parser con los métodos callback, una
	 * descripción del documento que está siendo parseado, y el tamaño del
	 * documento para informar respecto a pasos del porcentaje.
	 * 
	 * @param parser el objeto parser.
	 * @param document una cadena con el nombre (descriptivo) del documento
	 * que está siendo analizado.
	 * @param documentSize 
	 */
	public TextSegmentDigester(ODTParser parser,
			String document, long documentSize) {
		this.parserx = parser;
		this.document = document;
		this.documentSize = documentSize;
	}
	/**
	 * <p style="text-align:justify">
	 * El texto encontrado en el elemento actual desde el último elemento hijo.
	 */
	protected StringBuffer currTextSegment = new StringBuffer();

	/**
	 * <p style="text-align:justify">
	 * Procesa la notificación de caracteres recibidos desde el cuerpo del 
	 * elemento XML.
	 * <p style="text-align:justify">
	 * @param buffer los caracteres del documento XML
	 * @param start posición inicial dentro del buffer
	 * @param length cantidad de caracteres del buffer
	 *
	 * @exception SAXException si el parser SAX reportó una exepción
	 */
	@Override
	public void characters(char buffer[], int start, int length)
			throws SAXException {
		super.characters(buffer, start, length);
		parsed += length;
		currTextSegment.append(buffer, start, length);
		notifyParser();
	}

	/**
	 * <p style="text-align:justify">
	 * Notifica el final del análisis del documento.
	 * 
	 * @throws org.xml.sax.SAXException si el parser SAX reportó una excepción.
	 */
	@Override
	public void endDocument() throws SAXException {
		super.endDocument();
		parsed = documentSize;
		notifyParser();
		parserx.updateFinished(document);
	}

	/**
	 * <p style="text-align:justify">
	 * Procesa la notificación de inicio de un elemento XML econtrado.
	 * <p style="text-align:justify">
	 * @param namespaceURI el URI del espacio de nombres, o una cadena
	 * vacía si el elemento no posee espacio de nombre o no se está ejecutando
	 * el procesamiento de espacios de nombre.
	 * @param localName el nombre local (sin prefijo), o una cadena vacía si
	 * el procesamiento de espacios de nombre no se está ejecutando.
	 * @param qName el nombre cualificado (sin prefijo), o una cadena vacía
	 * si no están disponibles los nombres cualificados.
	 * @param list los atributos asociados al alemento. Si no hay atributos, 
	 * retorna una lista de atributos vacía.
	 * @exception SAXException si el parser SAX reportó una exepción.
	 */
	@Override
	public void startElement(String namespaceURI,
			String localName,
			String qName,
			Attributes list) throws SAXException {

		handleTextSegments();

		// A diferencia de bodyText, el cual acumula elementos hijo,
		// curTextSegment aquí se limpia. Esto significa que que no es
		// necesario almacenarlo en la pila.
		currTextSegment.setLength(0);

		super.startElement(namespaceURI, localName, qName, list);
		parsed += qName.length();
		for (int i = 0; i < list.getLength(); i++) {
			parsed += list.getQName(i).length();
			parsed += list.getValue(i).length();
			parsed += 4;
		}
		parsed += 2;
		if (!st.empty()) {
			st.peek().setHasChildren();
		}
		notifyParser();
		st.push(new ElementHasChildren(qName));
	}

	/**
	 * <p style="text-align:justify">
	 * Procesa la notificación del final de un elemento XML econtrado.
	 * <p style="text-align:justify">
	 * @param namespaceURI el URI del espacio de nombres, o una cadena
	 * vacía si el elemento no posee espacio de nombre o no se está ejecutando
	 * el procesamiento de espacios de nombre.
	 * @param localName el nombre local (sin prefijo), o una cadena vacía si
	 * el procesamiento de espacios de nombre no se está ejecutando.
	 * @param qName el nombre cualificado (sin prefijo), o una cadena vacía
	 * si no están disponibles los nombres cualificados.
	 * @exception SAXException si el parser SAX reportó una exepción.	 */
	@Override
	public void endElement(String namespaceURI,
			String localName,
			String qName)
			throws SAXException {

		handleTextSegments();
		currTextSegment.setLength(0);
		super.endElement(namespaceURI, localName, qName);
		if (st.pop().hasChildren()) {
			parsed += qName.length();
			parsed += 3;
		} else {
			parsed += 1;
		}
		notifyParser();
	}

	/**
	 * <p style="text-align:justify">
	 * Itera sobre la lista de reglas que se cumplieron en forma reciente, y si una
	 * de ellas implementa la interfaz {@link TextSegmentHandler}, invoca al
	 * método {@link TextSegmentHandler#textSegment(String)} pasando el
	 * segmento de texto del cuerpo del elemento XML.
	 */
	private void handleTextSegments() throws SAXException {
		if (currTextSegment.length() > 0) {
			String segment = currTextSegment.toString();
			List parentMatches = (List) matches.peek();
			int len = parentMatches.size();
			for (int i = 0; i < len; ++i) {
				Rule r = (Rule) parentMatches.get(i);
				if (r instanceof TextSegmentHandler) {
					TextSegmentHandler h = (TextSegmentHandler) r;
					try {
						h.textSegment(segment);
					} catch (Exception e) {
						throw createSAXException(e);
					}
				}
			}
		}
	}

	/**
	 * <p style="text-align:justify">
	 * Notifica al observador el porcentaje del documento analizado.
	 */
	private void notifyParser() {
		int p = ((int) (((parsed * 100) / documentSize) / 10)) * 10;
		if (currentPercent != p) {
			parserx.updateParsed(document, p);
			currentPercent = p;
		}
	}
	
//	@Override
//	public void addSetNext(String pattern, String method) {
//        addRule(pattern,
//                new SetNextRule(method));		
//	}
	
	private class ElementHasChildren {

		private String element;
		private boolean childs;

		private ElementHasChildren(String element) {
			this.element = element;
		}

		private void setHasChildren() {
			childs = true;
		}

		private boolean hasChildren() {
			return childs;
		}
	}
}
